import pandas as pd
import matplotlib.pyplot as plt
plt.rcParams['font.family'] = 'SimHei'
plt.rcParams['font.size'] = 15
from sklearn.model_selection import train_test_split, GridSearchCV      # 用于模型的数据集划分、交叉验证网格搜索
from sklearn.preprocessing import StandardScaler                        # 特征工程之标准化
from utils.data_util import preprocessing                               # 自定义的工具包之数据读取并处理
from utils.log import Logger                                            # 自定义的工具包之日志记录
from sklearn.metrics import mean_squared_error, mean_absolute_error     # 回归问题的评估方式
from xgboost import XGBRegressor
import joblib   # 用于模型的保存、加载...

# 1、创建日志对象，用来保存日志消息
logger_obj = Logger("../", log_name="train")  # 实例化日志类对象
logger = logger_obj.get_logger()    # 调用日志方法

# 2、初始化读取文件数据
def init():
    df = preprocessing("../data/train.csv")
    return df


# 3、进行eda操作、对数据进行探索，提取特征之后，那些细化特征对目标值有强关联
def eda(df):
    logger.info("开始进行数据探索")

    # 1.创建画布
    fig = plt.figure(figsize=(20, 40), dpi=150)

    # 2- 数据探索
    # 2.1- 绘制用电量的直方图
    """
        add_subplot(4,1,1)
            4：画布分为4行
            1：画布分为1列
            1：第一个图形展示的索引位置。注意：索引从1开始
    """
    g1 = fig.add_subplot(4,1,1)
    g1.hist(df["power_load"], bins=100,rwidth=0.80)
    g1.set_title("用电量的直方图")
    # fig.show()

    # 2.2- 12个月份和负荷的关系
    # (1)获取到月份
    df["month"] = pd.to_datetime(df["time"]).dt.month
    print(df.head(10))

    # (2)按照月份分组，求每个月份的均值
    """
        需要设置as_index为False，因为后续plot的时候是将month作为字段使用。否则会报如下的错
        ValueError: 'power_load' is neither a data key nor a valid format string (two marker symbols)
    """

    month_df = df.groupby(["month"],as_index=False)["power_load"].mean()
    print(month_df)
    g2 = fig.add_subplot(4,1,2)
    g2.plot("month", "power_load", data=month_df)
    g2.set_title("12个月份和负荷的关系")
    # fig.show()

    # 3— 24小时和负荷的关系
    # (1)获取到每个小时
    df["hour"] = pd.to_datetime(df["time"]).dt.hour
    print(df.head(10))

    # (2)按照月份分组，求每个月份的均值
    hour_df = df.groupby(["hour"], as_index=False)["power_load"].mean()
    print(hour_df)
    g3 = fig.add_subplot(4, 1, 3)
    g3.plot("hour", "power_load", data=hour_df)
    g3.set_title("24小时和负荷的关系")
    fig.show()

    logger.info("完成数据探索")

    # 4- 节假日和工作日与负荷的关系
    # def is_holiday(val):
    #     # time先转成日期时间对象 -> 获取是周几
    #     weekday = pd.to_datetime(val).weekday()
    #
    #     # 判断是否是节假日
    #     # 返回值含义：1表示节假日；0表示工作日
    #     if weekday >= 5:
    #         return 1
    #     else:
    #         return 0
    #
    # df["is_holiday"] = df["time"].apply(is_holiday)

    df["is_holiday"] = df["time"].apply(lambda x: 1 if pd.to_datetime(x).weekday() >= 5 else 0)
    print(df.head(200))

    # 分别计算节假日（周六、周日）、工作日（周一至周五）的用电负荷
    workday_df = df[df["is_holiday"] == 0]["power_load"].mean()
    weekday_df = df[df["is_holiday"] == 1]["power_load"].mean()

    # 绘制图形
    graph_4 = fig.add_subplot(4, 1, 4)
    graph_4.bar(x=["工作日", "节假日"], height=[workday_df, weekday_df])
    graph_4.set_title("节假日和工作日与负荷的关系")

    fig.show()


# 4、进行特征工程（特征选择、特征组合）
def feature_extract(df):

    logger.info("开始进行特征提取")

    # 1.基于time列获取到月份和小时
    df["month"] = pd.to_datetime(df["time"]).dt.month
    df["hour"] = pd.to_datetime(df["time"]).dt.hour

    # 2.获取昨天同一时间段的用电负荷
    # 2.1 将数据转换为字典类型 time作为key，power_load作为value
    time_load_dict = df.set_index("time")["power_load"].to_dict()
    # print(time_load_dict)

    # 2.2 基于当前日期获取到前一天同一时刻的日期
    # 注意：current_time、yesterday_time不是字符串类型，而是DateTime日期时间对象类型
    def get_yesterday(row, interval):
        # (1)获取到当前时间
        current_time = pd.to_datetime(row["time"])
        yesterday_time = current_time - pd.to_timedelta(interval)

        # (2)通过当前时刻获得前一天同一时刻电力负荷
        yesterday_time_str = yesterday_time.strftime("%Y-%m-%d %H:%M:%S")
        return time_load_dict.get(yesterday_time_str)

    # 对DataFrame中的数 据按行进行逐行处理
    df["yesterday"] = df.apply(get_yesterday,axis=1,args=("1d",))   # 间隔1天

    # 3- 获取当前时刻前1个小时、2个小时、3个小时的用电负荷
    # 实现方式一
    df["pre_1_hour"] = df.apply(get_yesterday,axis=1,args=("1h",))
    df["pre_2_hour"] = df.apply(get_yesterday,axis=1,args=("2h",))
    df["pre_3_hour"] = df.apply(get_yesterday,axis=1,args=("3h",))

    # 实现方式二
    # hours = 3
    # shift_list = [df["power_load"].shift(i) for i in range(1, hours + 1)]
    # shift_data = pd.concat(shift_list, axis=1)
    # shift_data.columns = [f"pre_{i}_hour" for i in range(1, hours + 1)]

    logger.info("特征提取完成")
    return df


def train(df):
    logger.info("开始进行模型训练")

    # 1.数据集已经获取到df

    # 2.获得特征、目标值
    y = df["power_load"]
    df1 = df.drop(["power_load","time"], axis=1)
    # print(df1.head())
    x = df1.iloc[:, :]

    # 3.划分数据集
    x_train, x_test, y_train, y_test = train_test_split(x, y, test_size=0.2,random_state=107)

    # 4.特征预处理（标准化）
    transformer = StandardScaler()
    x_train = transformer.fit_transform(x_train)
    x_test = transformer.transform(x_test)

    # 5.模型构建、训练
    model = XGBRegressor(n_estimators=50, max_depth=5, random_state=107)
    model.fit(x_train, y_train)

    # # 5.2 进行交叉验证、网格搜索
    # param_dict = {
    #     "max_depth": [i * 5 for i in range(1, 5)],
    #     "n_estimators": [i * 50 for i in range(1, 5)],
    #     "random_state": [107],
    #
    # }
    # model = GridSearchCV(estimator=model, param_grid=param_dict, cv=4,)
    # model.fit(x_train, y_train)
    # print(f"最优的超参数为：{model.best_params_}")
    # logger.info(f"最优的超参数组合为：{model.best_params_}")

    # 6.模型评估
    y_predict = model.predict(x_test)
    # print("模型训练_MSE", mean_squared_error(y_test, y_predict))
    # print("模型训练_MAE", mean_absolute_error(y_test, y_predict))

    logger.info(f"模型训练_MSE为：{mean_squared_error(y_test, y_predict)}")
    logger.info(f"模型训练_MAE为：{mean_absolute_error(y_test, y_predict)}")

    # 6- 保存训练好的模型
    # joblib.dump(model, "../model/power_load_good_finally.pkl")
    # joblib.dump(transformer, "../model/transformer_good_finally.pkl")

    logger.info("完成模型训练!!!")


if __name__ == '__main__':
    # 1.初始化：数据基本处理
    init_df = init()

    # 2.数据探索
    # eda(init_df)

    # 3.特征工程
    df = feature_extract(init_df)
    # print(new_df.head(30))

    # 4.模型训练
    train(df)
